﻿using System.Collections.Generic;

namespace GlobalStringFormatter
{
    /// <summary>
    /// A FormatterSystem is a collection of predicate/output function pairs.
    /// As a whole a FormatterSystem represents how to format the string output
    /// of a type of data.
    /// </summary>
    /// <typeparam name="TType">Any class-type. Usually a value-type like double, int, or string.</typeparam>
    public class FormatterSystem<TType>
    {
        private readonly List<Formatter<TType>> _formatters;

        /// <summary>
        /// A delegate type used to output the default value if none of the predicates match.
        /// </summary>
        /// <param name="value">The global string's raw value.</param>
        /// <returns></returns>
        public delegate string DefaultValue(TType value);

        /// <summary>
        /// The Default will be called if none of the predicates match.
        /// </summary>
        public DefaultValue Default;

        #region Constructors

        /// <summary>
        /// Copy constructor to create a new FormatterSystem.
        /// </summary>
        /// <param name="formatterSystem">The FormatterSystem to be copied.</param>
        public FormatterSystem(FormatterSystem<TType> formatterSystem)
        {
            _formatters = new List<Formatter<TType>>(formatterSystem._formatters);
            Default = (DefaultValue) formatterSystem.Default.Clone();
        }

        /// <summary>
        /// Empty constructor. Has no predicates. Outputs string.Empty by default.
        /// </summary>
        public FormatterSystem()
        {
            _formatters = new List<Formatter<TType>>();
            Default = v => string.Empty;
        }

        /// <summary>
        /// Constructor with a Default delegate but no predicates.
        /// </summary>
        /// <param name="default">A DefaultValue function to be called if no predicates match.</param>
        public FormatterSystem(DefaultValue @default)
        {
            _formatters = new List<Formatter<TType>>();
            Default = @default;
        }

        #endregion

        #region Public

        /// <summary>
        /// Adds a new Formatter based on a predicate and an output delegate. Attaches
        /// the new Formatter to FormatterSystem.
        /// </summary>
        /// <param name="predicate">
        /// Predicate delegate returns true for a given condition on the raw value.
        /// FormatterSystem will use the format delegate if the predicate is true.
        /// </param>
        /// <param name="format">Returns a formatted string based on the raw value.</param>
        public void Add(Formatter<TType>.Predicate predicate, Formatter<TType>.Format format)
        {
            _formatters.Add(new Formatter<TType>(predicate, format));
        }

        /// <summary>
        /// Adds a few Formatter based on a predicate, an output delegate, and a priority.
        /// A higher priority ensures that the predicate is called before others with lower
        /// priorities.
        /// </summary>
        /// <param name="predicate">
        /// Predicate delegate returns true for a given condition on the raw value.
        /// FormatterSystem will use the format delegate if the predicate is true.
        /// </param>
        /// <param name="format">Returns a formatted string based on the raw value.</param>
        /// <param name="priority">Priority of the predicate-format pair in the list of formatters. 0 is the highest priority.</param>
        public void Add(Formatter<TType>.Predicate predicate, Formatter<TType>.Format format, int priority)
        {
            _formatters.Insert(priority, new Formatter<TType>(predicate, format));
        }

        /// <summary>
        /// Adds a new Formatter to the end of the list of formatters.
        /// </summary>
        /// <param name="formatter"></param>
        public void Add(Formatter<TType> formatter)
        {
            _formatters.Add(formatter);
        }

        /// <summary>
        /// Adds a new Formatter to the list with a specified priority.
        /// </summary>
        /// <param name="formatter"></param>
        /// <param name="priority">Priority of the predicate-format pair in the list of formatters. 0 is the highest priority.</param>
        public void Add(Formatter<TType> formatter, int priority)
        {
            _formatters.Insert(priority, formatter);
        }

        /// <summary>
        /// Returns a copy of the list of Formatters. Modifying this list will have no effect on FormatterSystem.
        /// </summary>
        /// <returns>New list of Formatters.</returns>
        public List<Formatter<TType>> GetFormatters()
        {
            return new List<Formatter<TType>>(_formatters);
        }

        /// <summary>
        /// Removes the Formatter at a given priority. Will throw an exception if the priority isn't valid.
        /// </summary>
        /// <param name="priority">Priority of the predicate-format pair in the list of formatters. 0 is the highest priority.</param>
        public void Remove(int priority)
        {
            _formatters.RemoveAt(priority);
        }

        /// <summary>
        /// Removes a Formatter in the list.
        /// </summary>
        /// <param name="formatter"></param>
        public void Remove(Formatter<TType> formatter)
        {
            _formatters.Remove(formatter);
        }

        /// <summary>
        /// Replaces a Formatter at a given priority with a new one with the predicate-format delegate pair.
        /// </summary>
        /// <param name="priority">Priority of the predicate-format pair in the list of formatters. 0 is the highest priority.</param>
        /// <param name="predicate">
        /// Predicate delegate returns true for a given condition on the raw value.
        /// FormatterSystem will use the format delegate if the predicate is true.
        /// </param>
        /// <param name="format">Returns a formatted string based on the raw value.</param>
        public void Replace(int priority, Formatter<TType>.Predicate predicate, Formatter<TType>.Format format)
        {
            _formatters[priority] = new Formatter<TType>(predicate, format);
        }

        /// <summary>
        /// Replaces a Formatter at a given priority with the specified Formatter.
        /// </summary>
        /// <param name="priority">Priority of the predicate-format pair in the list of formatters. 0 is the highest priority.</param>
        /// <param name="formatter"></param>
        public void Replace(int priority, Formatter<TType> formatter)
        {
            _formatters[priority] = formatter;
        }

        /// <summary>
        /// Clears the list of Formatters.
        /// </summary>
        public void Clear()
        {
            _formatters.Clear();
        }

        #endregion
    }
}
